package nl.xvr.server.web;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

public class RequestHandler extends Thread {

	private String webroot;
	private String defaultpage;
	private boolean dir_browsing;
	private Socket socket;
	private WebServerLogger logger;

	public RequestHandler(String webroot, String defaultpage,
			boolean dir_browsing, Socket socket, WebServerLogger logger) {
		this.webroot = webroot;
		this.defaultpage = defaultpage;
		this.dir_browsing = dir_browsing;
		this.socket = socket;
		this.logger = logger;
	}

	public void run() {
		handle();
	}

	private void handle() {
		String logEntry = this.getLogTime() + " - Request start\n";
		try {

			BufferedReader in = new BufferedReader(new InputStreamReader(
					socket.getInputStream()));

			// Lees de http-request-header
			String request = in.readLine();
			while (!in.readLine().equals(""))
				; // negeer rest van request, tot en met lege regel

			String url = parseUrlFromPath(request);

			logEntry += request + "\n";
			logEntry += this.socket.getLocalAddress().getHostAddress() + "\n";

			if (url != null) {
				File f = getFileFromPath(url, (webroot + url));
				if (f != null) {
					if (f.isDirectory()) {
						File file = new File(f.getPath() + "/"
								+ this.defaultpage);
						try {
							processFile(file);
						} catch (FileNotFoundException e) {
							if (dir_browsing) {
								showDirectoryBrowsing(f);
							} else {
								errorReport(socket, "403", "Forbidden",
										"You don't have permission to access the requested URL.");
							}
						}
					} else {
						try {
							processFile(f);
						} catch (FileNotFoundException e) {
							// file not found
							errorReport(socket, "404", "Not Found",
									"The requested URL was not found on this server.");
						}
					}
				}
			}
			socket.close();
		} catch (Exception e) {
			System.out.println("Caught Exception in RequestHandler: " + e);
		} finally {
			logEntry += this.getLogTime() + " - Request end\n";
			this.logger.log(logEntry);
		}
	}

	private String parseUrlFromPath(String request) throws IOException {
		String path = null;
		if (!request.startsWith("GET")
				|| request.length() < 14
				|| !(request.endsWith("HTTP/1.0") || request
						.endsWith("HTTP/1.1"))) {
			// bad request
			errorReport(socket, "400", "Bad Request",
					"Your browser sent a request that this server could not understand.");
		} else {
			String req = request.substring(4, request.length() - 9).trim();
			if (req.indexOf("..") != -1 || req.indexOf("/.ht") != -1
					|| req.endsWith("~")) {
				errorReport(socket, "403", "Forbidden",
						"You don't have permission to access the requested URL.");
			} else {
				path = req;
			}
		}
		return path;
	}

	private File getFileFromPath(String url, String path) {
		File f = new File(path);
		if (f.isDirectory() && !path.endsWith("/")) {
			DataOutputStream out = null;
			try {
				out = new DataOutputStream(socket.getOutputStream());
				// redirect browser if referring to directory without final '/'
				out.writeBytes("HTTP/1.0 301 Moved Permanently\r\n"
						+ "Location: http://"
						+ socket.getLocalAddress().getHostAddress() + ":"
						+ socket.getLocalPort() + url + "/\r\n\r\n");
				f = null;
			} catch (IOException e) {
				System.err.println(e);
			} finally {
				try {
					out.close();
				} catch (IOException e) {

				}
			}
		}
		return f;
	}

	private String getContentType(String path) {
		if (path.endsWith(".html") || path.endsWith(".htm"))
			return "text/html";
		else if (path.endsWith(".txt") || path.endsWith(".java"))
			return "text/plain";
		else if (path.endsWith(".gif"))
			return "image/gif";
		else if (path.endsWith(".class"))
			return "application/octet-stream";
		else if (path.endsWith(".jpg") || path.endsWith(".jpeg"))
			return "image/jpeg";
		else
			return "text/plain";
	}

	private void errorReport(Socket connection, String code, String title,
			String msg) {
		DataOutputStream out = null;
		try {
			out = new DataOutputStream(socket.getOutputStream());
			out.writeBytes("HTTP/1.0 "
					+ code
					+ " "
					+ title
					+ "\r\n"
					+ "\r\n"
					+ "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
					+ "<HTML><HEAD>\r\n" + "<TITLE>" + code + " " + title
					+ "</TITLE>\r\n" + "</HEAD><BODY>\r\n" + "<H1>" + title
					+ "</H1>\r\n" + msg + "<P>\r\n"
					+ "<HR><ADDRESS>FileServer 1.0 at "
					+ connection.getLocalAddress().getHostName() + " Port "
					+ connection.getLocalPort() + "</ADDRESS>\r\n"
					+ "</BODY></HTML>\r\n");
		} catch (IOException e) {
			System.err.println(e);
		} finally {
			try {
				out.close();
			} catch (IOException e) {

			}
		}
	}

	private void processFile(File f) throws FileNotFoundException {
		// send file
		InputStream file = new FileInputStream(f);
		String header = "HTTP/1.0 200 OK\r\n" + "Content-Type: "
				+ getContentType(f.getPath()) + "\r\n" + "Date: " + new Date()
				+ "\r\n" + "Server: FileServer 1.0\r\n\r\n";
		sendFile(header, file); // send raw file
	}

	private void sendFile(String header, InputStream file) {
		DataOutputStream out = null;
		try {
			out = new DataOutputStream(socket.getOutputStream());
			out.writeBytes(header);
			byte[] buffer = new byte[1000];
			while (file.available() > 0)
				out.write(buffer, 0, file.read(buffer));
		} catch (IOException e) {
			System.err.println(e);
		} finally {
			try {
				out.close();
			} catch (IOException e) {

			}
		}
	}

	private void showDirectoryBrowsing(File dir) {
		DataOutputStream out = null;
		try {
			out = new DataOutputStream(socket.getOutputStream());
			FilenameFilter filter = new FilenameFilter() {
				public boolean accept(File dir, String name) {
					return !name.startsWith(".");
				}
			};
			String[] children = dir.list(filter);

			out.writeBytes("HTTP/1.0 200 OK\r\n"
					+ "Content-Type: text/html\r\n" + "Date: " + new Date()
					+ "\r\n" + "Server: FileServer 1.0\r\n\r\n");
			out.writeBytes("<html>"
					+ "<head><title>Web Server - Directory Browsing</title></head>\r\n"
					+ "</body>\r\n" + "<h1>Directory Browsing</h1>\r\n"
					+ "<table width=\"450px\">\r\n");
			for (String child : children) {
				File temp = new File(dir.getPath() + "/" + child);
				if (!temp.isHidden()) {
					out.writeBytes("<tr>\r\n" + "<td colspan=\"2\"><a href=\""
							+ temp.getName() + "\">" + temp.getName()
							+ "</a></td>\r\n");
					if (!temp.isDirectory()) {
						out.writeBytes("<tr>\r\n" + "<td>Last modified: "
								+ new Date(temp.lastModified()) + "</td>\r\n"
								+ "<td>Size: " + temp.length()
								+ " Bytes</td>\r\n");

					}
					out.writeBytes("</tr><tr><td colspan=\"2\">&nbsp</td></tr>\r\n");
				}
			}
			out.writeBytes("</table>\r\n" + "</body>\r\n" + "</html>\r\n");
		} catch (IOException e) {
			System.err.println(e);
		} finally {
			try {
				out.close();
			} catch (IOException e) {

			}
		}
	}

	private String getLogTime() {
		Calendar calendar = Calendar.getInstance(new Locale("nl", "NL"));

		return calendar.get(Calendar.DAY_OF_MONTH) + "-"
				+ calendar.get(Calendar.MONTH) + "-"
				+ calendar.get(Calendar.YEAR) + " "
				+ calendar.get(Calendar.HOUR_OF_DAY) + ":"
				+ calendar.get(Calendar.MINUTE) + ":"
				+ calendar.get(Calendar.SECOND);
	}
}
